package com.ceair.flight.refund.service.impl;

import com.ceair.flight.refund.dto.ResponseConst;
import com.ceair.flight.refund.dto.external.*;
import com.ceair.flight.refund.location.dao.IFlightOrderDao;
import com.ceair.flight.refund.location.dao.IPaxDao;
import com.ceair.flight.refund.location.dt.enums.ApplyReason;
import com.ceair.flight.refund.location.dt.enums.MethodEnum;
import com.ceair.flight.refund.location.dt.enums.RefundApplyUserType;
import com.ceair.flight.refund.location.dt.enums.RefundReasonEnum;
import com.ceair.flight.refund.location.dt.enums.RefundResConst;
import com.ceair.flight.refund.location.model.FlightOrderDO;
import com.ceair.flight.refund.location.model.RefundApplyDO;
import com.ceair.flight.refund.location.model.RefundTktDO;
import com.ceair.flight.refund.service.IRefundTicketService;
import com.ceair.flight.refund.service.external.PnrReleaseOrderServiceImpl;
import com.ceair.flight.refund.service.external.PnrReleaseService;
import com.ceair.flight.refund.service.external.RefundApplySaverServiceImpl;
import com.ceair.flight.refund.service.external.RefundApplyUpdaterServiceImpl;
import com.ceair.flight.refund.service.external.TicketRefundOrderServiceImpl;
import com.ceair.flight.refund.service.external.TicketRefundService;
import com.ceair.flight.refund.service.external.calculator.IRefundCalculatorService;
import com.ceair.flight.refund.service.external.refundcheck.IRefundableCheckerService;
import com.ceair.flight.refund.service.notification.INotificationPointCut;
import com.ceair.flight.refund.service.notification.IRefundFailNotification;
import com.ceair.flight.refund.util.GsonUtil;
import com.ceair.muservice.emd.dto.AncillaryRefundReq;
import com.ceair.muservice.emd.feign.client.IFnEmd;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Service("refundTicketService")
@Path("/refundTicket")
public class RefundTicketServiceImpl implements IRefundTicketService {

    private static final Logger LOG = LoggerFactory.getLogger(RefundTicketServiceImpl.class);
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    private IRefundableCheckerService refundableCheckerService;

    @Resource(name = "volRefundCalculatorServiceImpl")
    private IRefundCalculatorService volCalculatorService;
    @Resource(name = "involRefundCalculatorServiceImpl")
    private IRefundCalculatorService involCalculatorService;
    @Autowired
    private RefundApplySaverServiceImpl refundApplySaver;
    @Autowired
    private PnrReleaseService pnrReleaseService;
    @Autowired
    private TicketRefundService refundService;
    @Autowired
    private TicketRefundOrderServiceImpl ticketRefundOrderService;
    @Autowired
    private RefundApplyUpdaterServiceImpl refundApplyUpdaterService;
    @Autowired
    private PnrReleaseOrderServiceImpl pnrReleaseOrderService;
    @Autowired
    private IFlightOrderDao flightOrderDao;
    @Autowired
    private IPaxDao paxDao;
    @Autowired
    private IFnEmd fnEmd;
    @Autowired
    private INotificationPointCut notificationAspect;
    @Autowired
    private IRefundFailNotification refundFailNotification;

    @Override
    public boolean recalFeeService(RefundApplyDO refundApplyDO, RefundTktDO refundTktDO) {
        boolean result = false;
        SqlSession session = null;
        try {
            session = sqlSessionFactory.openSession(false);
            session.update("updateRefundApplyByApplyId", refundApplyDO);
            session.update("updateById", refundTktDO);
            session.commit(true);
            result = true;
        } catch (Exception e) {
            LOG.error("recalFeeService error:", e);
            session.rollback(true);
        } finally {
            session.close();
        }
        return result;
    }

    @Override
    @POST
    @Path("/refundApply")
    @Consumes({"application/json"})
    @Produces(MediaType.APPLICATION_JSON)
    public ApplyResponse applyRfdTkt(ApplyRequest applyRequest) {
        ApplyResponse res = new ApplyResponse(false);
        LOG.debug("Refund apply user request: {}", applyRequest);
        try {
            applyRequest.check();
            res.setResultCode(ResponseConst.SUCCESS_CODE);
            res.setResultMsg(ResponseConst.SUCCESS_MSG);
        } catch (IllegalArgumentException e) {
            res.setResultCode(RefundResConst.ARG_NOT_EXIST_CODE);
            res.setResultMsg(RefundResConst.getExceptionMsg(e.getMessage(), RefundResConst.ARG_NOT_EXIST_MSG));
            return res;
        }

        try {
            RefundApplyRequest refundApplyRequest = checkRefundableAndPrice(applyRequest);
            RefundApplyResult applyResult = null;
            RefundPriceResultList refundPriceResultList = null;
            try {
                applyResult = applyRefund(refundApplyRequest);
            } finally {
                //clear cache
                if (applyResult.isSuccess()) {
                    refundPriceResultList = buildRefundPriceResult(applyRequest);
                }
                ExternalRequestFactory.clear(refundApplyRequest);
            }
            return buildResult(applyResult, refundPriceResultList);
        } catch (ServiceException e) {
            refundFailNotification.sendRefundFailMail(flightOrderDao.getByFlightOrderNo(applyRequest.getFlightOrderNo()).getOrderNo());
            res.setResultCode(e.getMessageCode());
            res.setResultMsg(e.getMessage());
            return res;
        } catch (IllegalStateException e) {
            refundFailNotification.sendRefundFailMail(flightOrderDao.getByFlightOrderNo(applyRequest.getFlightOrderNo()).getOrderNo());
            res.setResultCode(RefundResConst.ILL_STATE_CODE);
            res.setResultMsg(RefundResConst.getExceptionMsg(e.getMessage(), RefundResConst.ILL_STATE_MSG));
            return res;
        }
    }

    private RefundPriceResultList buildRefundPriceResult(ApplyRequest applyRequest) {
        BaseRefundRequest refundableRequest = RequestBuilder.INSTANCE.buildTicketRefundRequest(applyRequest);
        ExternalRequest externalRequest = ExternalRequestFactory.getExternalRequest(refundableRequest);
        RefundablePriceList refundablePriceList = externalRequest.getRefundablePriceList();
        RefundablePriceList involResult = null;
        RefundablePriceList volResult = null;

        RefundableResultList refundableResultList = externalRequest.getRefundableResultList();
        if (ApplyReason.PERSONAL.getCode().equals(applyRequest.getClaimReason().getCode())) {
            volResult = refundablePriceList;
        } else {
            involResult = refundablePriceList;
        }
        RefundPriceResultList refundPriceResultList = new RefundPriceResultList(refundableRequest.getTickets().size());
        refundPriceResultList = ResultBuilder.INSTANCE.buildRefundPriceResult(refundPriceResultList, involResult, volResult);
        refundPriceResultList.setRefundableResult(refundableResultList);
        return refundPriceResultList;
    }

    @Override
    public RefundApplyResult applyRefund(RefundApplyRequest request) {
        return internalApplyRefund(request);
    }

    private RefundApplyResult internalApplyRefund(RefundApplyRequest refundApplyReq) {
        LOG.info("Refund ticket request: {}", refundApplyReq);
        String flightOrderNo = refundApplyReq.getFlightOrderNo();
        boolean isInitialed = ExternalRequestFactory.isRequestInitialed(refundApplyReq);
        if (!isInitialed) {
            LOG.error("Refund apply request was not initialed");
            throw new IllegalStateException("Refund apply request was not initialed");
        }
        RefundApplyResult result = new RefundApplyResult();
        // 退非航产品
        FlightOrderDO flightOrderDO = flightOrderDao.getByFlightOrderNo(flightOrderNo);
        if (flightOrderDO == null) {
            LOG.error("cannot found order by flightOrderNo:{}", flightOrderNo);
            throw new IllegalStateException("cannot find order by flightOrderNo");
        }
        Set<String> tickets = refundApplyReq.getTickets();
        //退非航产品,调用期间异常，应当继续执行退票。
        AncillaryRefundReq ancillaryRefundReq = null;
        try {
            if (!CollectionUtils.isEmpty(tickets)) {
                List<String> types = new ArrayList<>();
                types.add("emd");
                types.add("wifi");
                types.add("baggage");
                types.add("gaotie");
                ancillaryRefundReq = new AncillaryRefundReq();
                ancillaryRefundReq.setTypes(types);
                ancillaryRefundReq.setSalesChannel(refundApplyReq.getChannelNo());
                for (String ticket : tickets) {
                    ancillaryRefundReq.setAirTicketNo(ticket);
                    LOG.info("ancillaryRefundReq :{}", GsonUtil.toJson(ancillaryRefundReq));
                    fnEmd.refundAncillaryOrders(ancillaryRefundReq);
                }
            }
        } catch (Exception e) {
            LOG.error("ancillaryRefundReq :{}", GsonUtil.toJson(ancillaryRefundReq), e);
        }

        //保存退票申请信息
        Set<RefundApply> refundApplys = refundApplySaver.saveRefundApply(refundApplyReq);
        //取消座位
        try {
            releasePnr(refundApplyReq, refundApplys);
        } catch (Exception ex) {
            LOG.error("Release PNR error", ex);
        }

        //更改票面状态
        try {
            MultiRes multiRes = refundTicket(refundApplyReq, refundApplys);
            result.setSuccess(multiRes.isSuccess());
            //发送邮件通知
            notificationAspect.notifyPax4Refund(refundApplys, multiRes);
        } catch (Exception ex) {
            LOG.error("Modify ticket status error", ex);
        }

        return result;
    }

    MultiRes refundTicket(RefundApplyRequest applyRequest, Set<RefundApply> applys) {
        MultiRes multiRes = refundService.refundTickets(applys);
        try {
            ticketRefundOrderService.updateOrder(applyRequest, applys, multiRes);
            refundApplyUpdaterService.updateApply(applys, multiRes);
        } catch (Exception ex) {
            LOG.error("Update order status or refund apply status error after refund ticket", ex);
        }
        return multiRes;
    }

    void releasePnr(RefundApplyRequest applyRequest, Set<RefundApply> applys) {
        ReleasePnrRes releasePnrRes = pnrReleaseService.releaseSeats(applyRequest);
        pnrReleaseOrderService.update4ReleaseSeats(applys, releasePnrRes);
    }

    private ApplyResponse buildResult(RefundApplyResult applyResult, RefundPriceResultList refundPriceResultList) {
        return new ApplyResponse(applyResult.isSuccess(), refundPriceResultList);
    }

    private RefundApplyRequest checkRefundableAndPrice(ApplyRequest applyRequest) {
        LOG.info("Calculate refund price user request: {}", applyRequest);
        LOG.info("Calculate refund price user request's createId: {}", applyRequest.getCreateId());
        BaseRefundRequest refundableRequest = RequestBuilder.INSTANCE.buildTicketRefundRequest(applyRequest);
        TicketRefundableResult refundableResult = refundableCheckerService.checkRefundable(refundableRequest);
        LOG.debug("Ticket refundable checker result: {}", refundableResult);
        if (!refundableResult.isAllRefundableInPolicy()) {
            LOG.error("Unrefundable result: {}", refundableResult);
            //clear cache
            ExternalRequestFactory.clear(refundableRequest);
            throw new IllegalStateException("Unrefundable ticket");
        } else if (applyRequest.getClaimReason() == ApplyReason.PERSONAL && !refundableResult.isAllRefundableInRule()) {
            LOG.warn("Refund apply reason is not correct because the refund rule not permit");
        }
        RefundableResultList refundableResultList = ResultBuilder.INSTANCE.buildRefundableResult(refundableResult);
        RefundPriceRequest refundPriceRequest = RequestBuilder.INSTANCE.buildRefundPriceRequest(applyRequest, refundableResult);
        populateRefundPrice(applyRequest, refundPriceRequest);
        ExternalRequest externalRequest = ExternalRequestFactory.getExternalRequest(refundPriceRequest);
        externalRequest.setRefundableResultList(refundableResultList);
        //build request for applying refund ticket
        return buildApplyRequest(applyRequest, refundPriceRequest);
    }

    private void populateRefundPrice(ApplyRequest applyRequest, RefundPriceRequest refundPriceRequest) {
        //build request for refunding price calculation
        IRefundCalculatorService calculatorService = getCalculator(applyRequest);
        RefundablePriceList priceList = calculatorService.calculate(refundPriceRequest, MethodEnum.REFUND_APPLY);
        //set refundable price into cache
        ExternalRequest externalRequest = ExternalRequestFactory.getExternalRequest(refundPriceRequest);
        externalRequest.setRefundablePriceList(priceList);
    }

    private RefundApplyRequest buildApplyRequest(ApplyRequest applyRequest, RefundPriceRequest refundPriceRequest) {
        String flightOrderNo = applyRequest.getFlightOrderNo();
        Set<String> tickets = applyRequest.getTickets();
        RefundApplyRequest refundApplyRequest = new RefundApplyRequest(flightOrderNo, tickets);
        String[] ignoreProperties = new String[]{"claimerType", "claimReason", "attachments", "tickets"};
        BeanUtils.copyProperties(applyRequest, refundApplyRequest, ignoreProperties);
        buildAttachements(applyRequest, refundApplyRequest);
        refundApplyRequest.setClaimerType(RefundApplyUserType.getByCode(applyRequest.getClaimerType().getCode()));
        refundApplyRequest.setClaimReason(RefundReasonEnum.getByCode(applyRequest.getClaimReason().getCode()));
        for (String ticketNo : tickets) {
            int refundableIndex = refundPriceRequest.getRefundIndex(ticketNo);
            Date pnrCancelDate = refundPriceRequest.getPnrCancelDate(ticketNo);
            refundApplyRequest.setRefundIndex(ticketNo, refundableIndex);
            refundApplyRequest.setPnrCancelDate(ticketNo, pnrCancelDate);
        }
        return refundApplyRequest;
    }

    private IRefundCalculatorService getCalculator(ApplyRequest applyRequest) {
        boolean isVol = applyRequest.getClaimReason().isVoluntary();
        return isVol ? volCalculatorService : involCalculatorService;
    }

    private void buildAttachements(ApplyRequest applyRequest, RefundApplyRequest refundApplyRequest) {
        List<RefundDocs> docs = applyRequest.getAttachments();
        if (docs != null && !docs.isEmpty()) {
            List<ProofDoc> attachments = new ArrayList<>(docs.size());
            ProofDoc proofDoc;
            for (RefundDocs doc : docs) {
                proofDoc = new ProofDoc();
                proofDoc.setAttchments(doc.getAttchments());
                proofDoc.setContentDesc(doc.getContentDesc());
                attachments.add(proofDoc);
            }
            refundApplyRequest.setAttachments(attachments);
        }
    }

}
